
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Random;
import java.util.Scanner;

public class Jugador{
	double[] pesos;
	// Numero de jugador (1 o 2)
	int nroJugador;
    double ajuste = 0.3;
    double coefDescuento = 0.8;
    int[][] tablerosSecuencia;
    ListaValoraciones listaValoraciones;
    int ladoTablero;
    int jugadas;
    public NeuralNetwork funcValoracion;
	
	/*
	 * Los tipos de juego son:
	 *  1) Aleatorio: Elige la jugada aleatoriamente
	 *  2) Humano: El ingreso del movimiento es efectuado mediante el teclado
	 *  3) Entrenando: Juega y va actualizando los pesos con el tiempo
	 *  4) Jugando y utilizando pesos: Utiliza los pesos que estï¿½n utilizando
	 * 
	 */
	int tipoDeJuego;
	
	Jugador(int parNroJugador, int parTipoDeJuego,int parLado){
		pesos = new double[]{0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0,5};
		nroJugador = parNroJugador;
		tipoDeJuego = parTipoDeJuego;
		tablerosSecuencia = new int[25][6];
		listaValoraciones = new ListaValoraciones();
		ladoTablero = parLado;
		jugadas = 0;
		funcValoracion = null;
	}

	Jugador(int parNroJugador, int parTipoDeJuego,int parLado, ListaValoraciones parListaValoraciones){
		pesos = new double[]{0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0,5};
		nroJugador = parNroJugador;
		tipoDeJuego = parTipoDeJuego;
		tablerosSecuencia = new int[25][6];
		listaValoraciones = new ListaValoraciones();
		ladoTablero = parLado;
		jugadas = 0;
		listaValoraciones = parListaValoraciones;
		funcValoracion = null;
	}
	
	// Getters
    public double[] getPesos() {
        return pesos;
    }

    public int getTipoDeJuego() {
        return tipoDeJuego;
    }

    // Setters
    public void setPesos(double[] parPesos) {
        pesos = parPesos;
    }

    public void setTipoDeJuego(int parTipoDeJuego) {
        tipoDeJuego = parTipoDeJuego;
    }
    
	public void elegirMovimiento(Tablero tablero){
		switch (tipoDeJuego){
		case 1: elegirMovimientoAleatorio(tablero);
				break;
		case 2: elegirMovimientoUsuario(tablero);
				break;
		case 3: elegirMovimientoEntrenamiento(tablero);
				break;
		case 4: elegirMovimientoConPeso(tablero);
				break;
		case 5: elegirMovimientoEntrenamientoRefuerzo(tablero, true);
				break;
		case 6: elegirMovimientoJugandoRefuerzo(tablero);
				break;
		}
	}

    public void elegirMovimientoConPeso(Tablero tablero) {
        double valorMaximo = -100;
        int indiceElegido = -1;
        double auxValTablero;
        for (int i = 0; i < tablero.numeroDeEspacios; i++) {
            if (tablero.getIndice(i) == ' ') {
                // Marco tablero provisoriamente para evaluarlo
                if (nroJugador == 1) {
                    tablero.marcarJugador1(i);
                } else {
                    tablero.marcarJugador2(i);
                }

                // Evaluo y reviso si es el mejor hasta el momento
            	auxValTablero = tablero.evaluarTablero(pesos,nroJugador);
                
                if (auxValTablero >= valorMaximo){
                	valorMaximo = auxValTablero;
                	indiceElegido = i;
                }
                // Desmarco tablero para dejarlo igual
                tablero.limpiarIndice(i);
            }
        }
        if (indiceElegido == -1) {
            System.out.println("ERRRRRRRRRRRRROOOOOOOOOR");
        }
        if (nroJugador == 1) {
            tablero.marcarJugador1(indiceElegido);
        } else {
            tablero.marcarJugador2(indiceElegido);
        }
    }

    public void elegirMovimientoAleatorio(Tablero tablero) {
        List<Integer> lugaresVacios = new ArrayList<Integer>();
        //busco los valores vacios
        for (int i = 0; i < tablero.numeroDeEspacios; i++) {
            if (tablero.getIndice(i) == ' ') {
                lugaresVacios.add(i);
            }
        }

        if (lugaresVacios.size() > 0) {
            Random r = new Random();
            int idx = r.nextInt(lugaresVacios.size());
            if (nroJugador == 1) {
                tablero.marcarJugador1(lugaresVacios.get(idx));
            } else {
                tablero.marcarJugador2(lugaresVacios.get(idx));
            }
        }
    }

    // Jugada mediante el teclado
    public void elegirMovimientoUsuario(Tablero tablero) {
        System.out.println("Ingrese el indice de la celda a marcar [0..24]:");
        Scanner in = new Scanner(System.in);
        int num;
        boolean terminado = false;
        while (!terminado) {
            try {
                num = in.nextInt();
                if ((num < tablero.numeroDeEspacios) && (num >= 0)) {
                    if (tablero.getIndice(num) == ' ') {
                        if (nroJugador == 1) {
                            tablero.marcarJugador1(num);
                        } else {
                            tablero.marcarJugador2(num);
                        }
                        terminado = true;
                    } else {
                        System.out.println("Celda ocupada. Ingrese una celda libre");
                    }
                } else {
                    System.out.println("Ingrese un indice valido");
                }
            } catch (InputMismatchException ex) {
                System.out.println("Ingrese un indice valido");
                in = new Scanner(System.in);
            }
        }
    }
	void elegirMovimientoEntrenamiento(Tablero tablero){
		// Para actualizar pesos evaluo el tablero tal como está, y evalúo el tablero 
		// del el siguiente turno en el que me vuelve a tocar, simulando también el movimiento del contrario
		// Para esto se asume que el contrario se movería igual que nosotros
		
		// Obtengo atributos antes de realizar modificaciones
		int atributos[] = tablero.getAtributos(tablero.getLineas());
		
		// Calculo el valor del tablero actual
		double puntajeTableroActual = tablero.evaluarTablero(pesos, nroJugador);
		
		// Hago mi movimiento de esta jugada pero con los pesos sin actualizar
		elegirMovimientoConPeso(tablero);
		
		// Voy a crear una copia del tablero para hacer la simulacion del movimiento
		// del adversario
		Tablero auxTablero = new Tablero();
		for (int i = 0; i < tablero.numeroDeEspacios; i++){
			auxTablero.marcarIndice(i, tablero.getIndice(i));
		}
		
		// Simulo movimiento del otro jugador en el tablero auxiliar
		// Temporalmente cambio el atributo nroJugador para que el procedimiento elegirMovimientoUsuario
		// modifique el tablero como si fuera el otro jugador
		// Solo se actualizan los pesos si el juego no esta terminado
		if (!auxTablero.juegoTerminado()){
			if (nroJugador == 1){
				nroJugador = 2;
				elegirMovimientoConPeso (auxTablero);
				nroJugador = 1;
			} else {
				nroJugador = 1;
				elegirMovimientoConPeso (auxTablero);
				nroJugador = 2;
			}
			double puntajeTableroSiguiente = auxTablero.evaluarTablero(pesos,nroJugador);
			
			pesos[0] = pesos[0] + ajuste * (puntajeTableroSiguiente - puntajeTableroActual); 	
			pesos[1] = pesos[1] + ajuste * (puntajeTableroSiguiente - puntajeTableroActual) * atributos[0]; 
			pesos[2] = pesos[2] + ajuste * (puntajeTableroSiguiente - puntajeTableroActual) * atributos[1]; 
			pesos[3] = pesos[3] + ajuste * (puntajeTableroSiguiente - puntajeTableroActual) * atributos[2]; 
			pesos[4] = pesos[4] + ajuste * (puntajeTableroSiguiente - puntajeTableroActual) * atributos[3]; 
			pesos[5] = pesos[5] + ajuste * (puntajeTableroSiguiente - puntajeTableroActual) * atributos[4]; 
			pesos[6] = pesos[6] + ajuste * (puntajeTableroSiguiente - puntajeTableroActual) * atributos[5]; 
		} 		
	}
	
	
	// Llamar a este procedimiento cuando se termina una partida para que el jugador actualice las 
	// valoraciones de los tableros
	// Solo se esta entrenando deberia pasarse el parametro actualizarValoraciones en true
	// Esto es para que se actualicen las valoraciones de la lista de tableros procesados en la jugada.
	public void terminaJuego(int ganador, boolean actualizarValoraciones, Tablero tablero){
		tablerosSecuencia[jugadas] = tablero.getAtributos(tablero.getLineas());
		jugadas++;
		
		if (actualizarValoraciones){
			//System.out.println("termina");
			int resultado = 0;
			if (ganador == nroJugador){
				resultado = 1;
			} else {
				if (ganador != 0){
					resultado = -1;
				}
			}
			//System.out.println("--------------");
			//System.out.println("Resultado: " + resultado);
			for (int i = 0; i < jugadas; i++){
				double nuevaValoracion = resultado*Math.pow(coefDescuento,jugadas-i-1);
				//System.out.println("Nueva valoracion: " + nuevaValoracion);
				if (listaValoraciones.existeValoracion(tablerosSecuencia[i])){
					listaValoraciones.actualizarValoracion(tablerosSecuencia[i], nuevaValoracion);
				} else {
					listaValoraciones.agregarValoracion(tablerosSecuencia[i], nuevaValoracion);
				}
			}
			//System.out.println("--------------");
		}
		iniciarPartida();
	}
	
	public void iniciarPartida(){
		tablerosSecuencia = new int[25][6];
		jugadas = 0;
	}
	
	
	public int elegirMovimientoEntrenamientoRefuerzo(Tablero tablero, boolean guardarHistorial){
		// Agrego los atributos del tablero al vector de "tableros"
		if (guardarHistorial){ 
			tablerosSecuencia[jugadas] = tablero.getAtributos(tablero.getLineas());
			jugadas++;
		}
		
		// Indice en el que se va a guardar el movimiento
		int indiceElegido = -1;
		
		// En este array se van a guardar los indices que se pueden seleccionar, los "vacios"
		int[] indices = new int[ladoTablero * ladoTablero];
		int maxIndice = -1;
		
		// En este array se van a guardar los valores asignados a cada uno, algo asi como 
		// el peso que tendra cada indice al momento de elegir, parecido a su probabilidad
		double[] probIndice = new double[ladoTablero * ladoTablero];
		
		// Variables auxiliares
		double totalValoraciones = 0;
		int[] atributosAux;
		
		int auxMarcaContrario = -1;
		
		for (int i = 0; i < (ladoTablero*ladoTablero); i++){
			if (tablero.getIndice(i) == ' '){
				// Debo agregar una entrada en los indices a elegir
				maxIndice++;
				indices[maxIndice] = i;
				
				// Simulo jugada para ver como queda el tablero
	            if (nroJugador == 1) {
	                tablero.marcarJugador1(i);
	            } else {
	            	tablero.marcarJugador2(i);
	            }
	            
	            // Si el juego no se termino despues de nuestra movida, voy a 
	            // simular tambien la del adversario
	            auxMarcaContrario = -1;
	            if (!tablero.juegoTerminado()){
	            	if (nroJugador == 1) {
		            	nroJugador = 2;
		            	auxMarcaContrario = elegirMovimientoSimulacionContrario(tablero);
		                nroJugador = 1;
		            } else {
		            	nroJugador = 1;
		            	auxMarcaContrario = elegirMovimientoSimulacionContrario(tablero);
		                nroJugador = 2;
		            }
	            }
	            
	            // Si se va a ganar se selecciona ese indice directamente
	            if (tablero.juegoTerminado() && (tablero.getGanador() == nroJugador)){
	            	indiceElegido = i;
	            	break;
	            } else {
		            // Calculo valor a guardar en el vector de "probabilidades" 
		            probIndice[maxIndice]  = 0;
		            atributosAux = tablero.getAtributos(tablero.getLineas());
		            if (listaValoraciones.existeValoracion(atributosAux)){
		            	probIndice[maxIndice] = listaValoraciones.obtenerValoracion(atributosAux);
		            }
		      
	            	// La puntuacion del tablero sera 3^(valoracion) para que de todo positivo
		            probIndice[maxIndice] = Math.pow(3, probIndice[maxIndice]);
		            totalValoraciones += probIndice[maxIndice];
		            
		            tablero.limpiarIndice(i);
	            }
	            if (auxMarcaContrario != -1){
	            	tablero.limpiarIndice(auxMarcaContrario);
	            }
			}
		}

		if (indiceElegido == -1){
			for (int i = 0; i <= maxIndice; i++){
				probIndice[i] = probIndice[i] * 100 / totalValoraciones;
			}
			
			// Genero numero randomico
			int rand = randInt(0,100);
			
			// Selecciono un tablero segun ese numero, teniendo en cuenta sus probabilidades
			int totProc = 0;
			for (int i = 0; i <= maxIndice; i++){
				if (rand <= (totProc + probIndice[i]*10)){
					indiceElegido = i;
					break;
				} else {
					totProc += probIndice[i];
				}
			}
		}
		
		// Marco la opcion que mas me conviene
		if (indiceElegido != -1){
	        if (nroJugador == 1) {
	            tablero.marcarJugador1(indices[indiceElegido]);
	        } else {
	            tablero.marcarJugador2(indices[indiceElegido]);
	        }
		}
		
        return indiceElegido;
	}
	
	public int elegirMovimientoJugandoRefuerzo(Tablero tablero){
		int indiceElegido = -1;
		double valorElegido = -2;
		int[] atributosAux;
		double valorAComparar = 0;
		double[] test = new double [6];

		int auxMarcaContrario;
		
		// Elijo jugada. Recorro el tablero y analizo cual es la jugada que me conviene.
		for (int i = 0; i < (ladoTablero*ladoTablero); i++){
			if (tablero.getIndice(i) == ' '){
	            if (nroJugador == 1) {
	                tablero.marcarJugador1(i);
	            } else {
	                tablero.marcarJugador2(i);
	            }
	            
	            auxMarcaContrario = -1;
	            // Si el juego no se termino despues de nuestra movida, voy a 
	            // simular tambien la del adversario
	            if (!tablero.juegoTerminado()){
	            	if (nroJugador == 1) {
		            	nroJugador = 2;
		            	auxMarcaContrario = elegirMovimientoSimulacionContrario(tablero);
		                nroJugador = 1;
		            } else {
		            	nroJugador = 1;
		            	auxMarcaContrario = elegirMovimientoSimulacionContrario(tablero);
		                nroJugador = 2;
		            }
	            }
	            
	            if (tablero.juegoTerminado() && (tablero.getGanador() == nroJugador)){
	            	indiceElegido = i;
	            	break;
	            } else {
		            // Se obtienen los 6 atributos del tablero
		            atributosAux = tablero.getAtributos(tablero.getLineas());

		            // Seteo parametro de la funcion de evaluacion
		    		test[0] = atributosAux[0]; 
		    		test[1] = atributosAux[1];
		    		test[2] = atributosAux[2];
		    		test[3] = atributosAux[3];
		    		test[4] = atributosAux[4];
		    		test[5] = atributosAux[5];
		    		
		    		// Evaluo tablero
		    		valorAComparar = funcValoracion.evaluate(test);
		            
		    		// Si es la mejor opcion hasta el momento la elijo
	            	if (valorAComparar > valorElegido){
	            		valorElegido = valorAComparar;
	            		indiceElegido = i;
	            	}
	            	
		            tablero.limpiarIndice(i);
	            }
	            
	            // Desmarco la simulacion del contrario
	            if (auxMarcaContrario != -1){
	            	tablero.limpiarIndice(auxMarcaContrario);
	            }
			}
		}
		// Marco la opcion que mas me conviene
	    if (indiceElegido != -1){
			if (nroJugador == 1) {
	            tablero.marcarJugador1(indiceElegido);
	        } else {
	            tablero.marcarJugador2(indiceElegido);
	        }
	    } else { 
	    	//System.out.println("Error: No se pudo marcar ninguna celda en la jugada");
	    }
	    return indiceElegido;
	}
	

    public int elegirMovimientoSimulacionContrario(Tablero tablero){
        int indiceElegido = -1;
        double valorElegido = 200;
        int[] atributosAux;
        double valorAComparar = 0;
        
        // Elijo jugada. Recorro el tablero y analizo cual es la jugada que me conviene.
        for (int i = 0; i < (ladoTablero*ladoTablero); i++){
            if (tablero.getIndice(i) == ' '){
	            if (nroJugador == 1) {
	                tablero.marcarJugador1(i);
	            } else {
	                tablero.marcarJugador2(i);
	            }
	            
	            if (tablero.juegoTerminado() && (tablero.getGanador() == nroJugador)){
	                indiceElegido = i;
	                break;
	            } else {
	                    // La variable valor a comparar es para que si no encuentra ningun tablero
	                    // igual juegue, pero seleccionando un tablero que no se haya encontado, al que se
	                    // le asigna entonces la valoracion 0 (no va a ser necesario cuando se tenga la red neuronal, porque se tendra
	                    // valoracion para todos, independientemente de si ya se encontro o no)

	                    valorAComparar = 0;
	                    atributosAux = tablero.getAtributos(tablero.getLineas());
	                    if (listaValoraciones.existeValoracion(atributosAux)){
	                        valorAComparar = listaValoraciones.obtenerValoracion(atributosAux);
	                    }
	                if (valorAComparar < valorElegido){
	                        valorElegido = valorAComparar;
	                        indiceElegido = i;
	                }
	                    tablero.limpiarIndice(i);
	            }
            }
        }
        // Marco la opcion que mas me conviene
	    if (indiceElegido != -1){
	        if (nroJugador == 1) {
	            tablero.marcarJugador1(indiceElegido);
	        } else {
	            tablero.marcarJugador2(indiceElegido);
	        }
	    }
	    return indiceElegido;
    }
	
	
	public int randInt(int min, int max) {
	    Random rand = new Random();
	    int randomNum = rand.nextInt((max - min) + 1) + min;
	    return randomNum;
	}
}